package com.jprocms.module.cms.framework.file.core.client.ftp;

import cn.hutool.core.io.FileUtil;
import cn.hutool.core.util.CharsetUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.extra.ftp.Ftp;
import cn.hutool.extra.ftp.FtpException;
import cn.hutool.extra.ftp.FtpMode;
import com.jpro.framework.common.exception.ServerException;
import com.jpro.module.infra.framework.file.core.client.ftp.FtpFileClientConfig;
import com.jprocms.module.cms.framework.file.core.client.AbstractFileClient;
import org.apache.commons.net.ftp.FTP;
import org.apache.commons.net.ftp.FTPClient;
import org.apache.commons.net.ftp.FTPFile;
import org.springframework.util.StringUtils;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.net.SocketException;

/**
 * Ftp 文件客户端
 *
 * @author JPROCMS
 */
public class FtpFileClient extends AbstractFileClient<FtpFileClientConfig> {

    private Ftp ftp;

    public FtpFileClient(Long id, FtpFileClientConfig config) {
        super(id, config);
    }

    @Override
    protected void doInit() {
        // 把配置的 \ 替换成 /, 如果路径配置 \a\test, 替换成 /a/test, 替换方法已经处理 null 情况
        config.setBasePath(StrUtil.replace(config.getBasePath(), StrUtil.BACKSLASH, StrUtil.SLASH));
        // ftp的路径是 / 结尾
        if (!config.getBasePath().endsWith(StrUtil.SLASH)) {
            config.setBasePath(config.getBasePath() + StrUtil.SLASH);
        }
        // 初始化 Ftp 对象
        this.ftp = new Ftp(config.getHost(), config.getPort(), config.getUsername(), config.getPassword(),
                CharsetUtil.CHARSET_UTF_8, null, null, FtpMode.valueOf(config.getMode()));
    }

    @Override
    public String upload(byte[] content, String path, String type) {
        // 执行写入
        String filePath = getFilePath(path);
        String fileName = FileUtil.getName(filePath);
        String dir = StrUtil.removeSuffix(filePath, fileName);
        ftp.reconnectIfTimeout();
        boolean success = ftp.upload(dir, fileName, new ByteArrayInputStream(content));
        if (!success) {
            throw new FtpException(StrUtil.format("上传文件到目标目录 ({}) 失败", filePath));
        }
        // 拼接返回路径
        return super.formatFileUrl(config.getDomain(), path);
    }

    @Override
    public boolean delete(String path) {
        String filePath = getFilePath(path);
        ftp.reconnectIfTimeout();
        return ftp.delFile(filePath);
    }

    @Override
    public byte[] getContent(String path) {
        String filePath = getFilePath(path);
        String fileName = FileUtil.getName(filePath);
        String dir = StrUtil.removeSuffix(filePath, fileName);
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        ftp.reconnectIfTimeout();
        ftp.download(dir, fileName, out);
        return out.toByteArray();
    }


    public boolean saveDir(String path) throws IOException, ServerException {
        boolean flag = false;
        FTPClient ftp = getFtp().getClient();
        if (!ftp.changeWorkingDirectory(path)) {
            String[] ps = StringUtils.split(path, SPT);
            String p = SPT;
            ftp.changeWorkingDirectory(p);
            for (String s : ps) {
                p += s + SPT;
                if (!ftp.changeWorkingDirectory(p)) {
                    ftp.makeDirectory(s);
                    flag = ftp.changeWorkingDirectory(p);
                }
            }
        }
        ftp.logout();
        ftp.disconnect();
        return flag;
    }


    /**
     * 修改文件名
     *
     * @param from 原文件名
     * @param to   修改后文件名
     * @return
     */
    public boolean reName(String from, String to) throws IOException, ServerException {
        FTPClient ftpClient = getFtp().getClient();
        boolean succ = ftpClient.rename(from, to);
        ftpClient.logout();
        ftpClient.disconnect();
        return succ;
    }

    /**
     * 复制文件夹以及文件夹下的全部内容
     *
     * @param sourceDir 源文件夹
     * @param targetDir 目标文件
     * @throws IOException 异常
     */
    public void copyDirectiory(String sourceDir, String targetDir) throws IOException {
        FTPClient ftpClient = ftp.getClient();
        // 新建目标目录
        if (!existDirectory(targetDir)) {
            createDirectory(targetDir);
        }
        // 获取源文件夹当前下的文件或目录
        // File[] file = (new File(sourceDir)).listFiles();
        FTPFile[] ftpFiles = ftpClient.listFiles(sourceDir);
        for (int i = 0; i < ftpFiles.length; i++) {
            if (ftpFiles[i].isFile()) {
                copyFile(ftpFiles[i].getName(), sourceDir, targetDir);
            } else if (ftpFiles[i].isDirectory()) {
                copyDirectiory(sourceDir + File.separatorChar + ftpFiles[i].getName(),
                        targetDir + File.separatorChar + ftpFiles[i].getName());
            }
        }
        ftpClient.logout();
        ftpClient.disconnect();
    }

    /**
     * 复制文件.
     *
     * @param sourceFileName 源文件名称
     * @param sourceDir      源文件夹
     * @param targetDir      目标文件夹
     * @throws IOException 异常
     */
    public void copyFile(String sourceFileName, String sourceDir, String targetDir) throws IOException {
        FTPClient ftpClient = ftp.getClient();
        ByteArrayInputStream in = null;
        ByteArrayOutputStream fos = new ByteArrayOutputStream();
        try {
            if (!existDirectory(targetDir)) {
                createDirectory(targetDir);
            }
            ftpClient.setBufferSize(1024 * 2);
            // 变更工作路径
            ftpClient.changeWorkingDirectory(sourceDir);
            // 设置以二进制流的方式传输
            ftpClient.setFileType(FTP.BINARY_FILE_TYPE);
            // 将文件读到内存中
            ftpClient.retrieveFile(new String(sourceFileName.getBytes("GBK"), "iso-8859-1"), fos);
            in = new ByteArrayInputStream(fos.toByteArray());
            if (in != null) {
                ftpClient.changeWorkingDirectory(targetDir);
                ftpClient.storeFile(new String(sourceFileName.getBytes("GBK"), "iso-8859-1"), in);
            }
        } finally {
            // 关闭流
            if (in != null) {
                in.close();
            }
            if (fos != null) {
                fos.close();
            }
            ftpClient.logout();
            ftpClient.disconnect();
        }
    }

    /**
     * 创建文件夹
     *
     * @param targetDir 目标文件夹
     * @throws IOException 异常
     * @Title: createDirectory
     */
    private void createDirectory(String targetDir) throws IOException {
        FTPClient ftpClient = ftp.getClient();
        ftpClient.makeDirectory(targetDir);
        ftpClient.logout();
        ftpClient.disconnect();
    }

    /**
     * 判断文件夹是否存在
     *
     * @param targetDir 目标文件夹
     * @throws IOException     异常
     * @throws SocketException 异常
     * @Title: existDirectory
     */
    private boolean existDirectory(String targetDir) throws SocketException, IOException {
        FTPClient ftpClient = ftp.getClient();
        boolean flag = false;
        FTPFile[] ftpFileArr = ftpClient.listFiles(targetDir);
        if (ftpFileArr.length > 0) {
            flag = true;
        }
        ftpClient.logout();
        ftpClient.disconnect();
        return flag;
    }

    private final static String SPT = "/";

    private String getFilePath(String path) {
        return config.getBasePath() + path;
    }

    public Ftp getFtp() {
        return ftp;
    }
}